﻿
using System.Collections.Generic; 
using UnityEngine;

namespace XFGameFramework.OctreeSystem
{

    public class OctreeManager  
    {
        public static bool Initialized { get;private set; }

        //public static OctreeArea Root { get;private set; }
         
        public static Dictionary<int,OctreeArea> Roots = new Dictionary<int,OctreeArea>();

        private static Dictionary<int,float> MinAreaSize = new Dictionary<int,float>();

        private static List<OctreeObject> raycastObjects = new List<OctreeObject>();

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        private static void OnStarted()
        {
            Roots.Clear();
        }
          
        public static void Add(OctreeObject obj) 
        {
            if (!obj || !obj.gameObject.activeInHierarchy || !obj.enabled)
                return;
             
            OctreeArea area = GetRoot(obj.gameObject.layer);

            // 判断是否需要扩容
            if (! Intersects(area.Bounds,obj)) 
            {
                // 当前游戏物体超出了区域 需要扩容
                Bounds bounds = area.Bounds;  

                do 
                {
                    // 扩容一倍
                    bounds.Expand(bounds.size.x);
                } 
                while (!Intersects(bounds, obj));

                OctreeArea root = new OctreeArea(null,bounds,area.Layer);

                root.Copy(area);
                area.Clear();
                area = root;
                Roots[area.Layer] = area;
            }

            area.Add(obj);
        }

        public static void Remove(OctreeObject obj,bool updateBounds = false) 
        {
            if (!obj) return;
            if (obj.Area == null) return;
            obj.Area.RemoveDirect(obj, updateBounds); 
        }

        public static void Get(Bounds bounds, List<OctreeObject> results,LayerMask layerMask)
        { 

            if (results == null)
                throw new System.Exception("result is null!");

            results.Clear();

            foreach (var layer in Roots.Keys)
            {
                if(layerMask.Contains(layer))
                    Roots[layer].Get(bounds, results);
            } 
        }

        public static void Get(Bounds bounds, List<OctreeObject> results )
        {
            Get(bounds, results, Physics.AllLayers);
        }

        public static void Get(Vector3 position, float radius, List<OctreeObject> results,LayerMask layerMask ) 
        { 
            Bounds bounds = new Bounds(position, new Vector3(radius * 2, radius * 2, radius * 2));
            Get(bounds, results, layerMask);

            for (int i = results.Count - 1; i >= 0; i--)
            { 
                if (results[i].ClosestDistance(position) > radius) 
                    results.RemoveAt(i); 
            }
        }

        public static void Get(Vector3 position, float radius, List<OctreeObject> results )
        {
            Get(position, radius, results, Physics.AllLayers);
        }


        public static void Get(OctreeBounds bounds, List<OctreeObject> results, LayerMask layerMask) 
        {
            Bounds b = bounds.GetBounds();
            Get(b, results, layerMask);
            for (int i = results.Count - 1; i >= 0; i--)
            {
                if (results[i].Intersects(bounds)) continue; 
                results.RemoveAt(i); 
            }
        }

        public static void Get(OctreeBounds bounds, List<OctreeObject> results) {
            Get(bounds, results, Physics.AllLayers);
        }


        public static void Get(IList<OctreeBounds> bounds, List<OctreeObject> results, LayerMask layerMask)
        {
            if (bounds == null || bounds.Count == 0) 
                return;

            Bounds b = new Bounds();

            foreach (var item in bounds)
            {
                b.Encapsulate(item.GetBounds());
            }
             
            Get(b, results, layerMask);

            for (int i = results.Count - 1; i >= 0; i--)
            { 
                if (Intersects(bounds, results[i])) 
                    continue;
                results.RemoveAt(i);
            }
        }

        public static void Get(IList<OctreeBounds> bounds, List<OctreeObject> results) {
            Get(bounds, results, Physics.AllLayers);
        }


        private static bool Intersects(IList<OctreeBounds> bounds, OctreeObject octreeObject) 
        {
            foreach (var item in bounds)
            {
                if (octreeObject.Intersects(item)) return true;
            }
            return false;
        }

        /// <summary>
        /// 判断游戏物体是否和某一个区域相交
        /// </summary>
        /// <param name="bounds"></param>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool Intersects(Bounds bounds,OctreeObject obj)
        { 
            return obj.Intersects(bounds);
        }

        public static bool Raycast(Ray ray, float distance, LayerMask layerMask, out IntersectInfo info) 
        {
            info = new IntersectInfo();

            if (distance <= 0)
                return false;

            Bounds bounds = new Bounds();
            bounds.Encapsulate(ray.origin);
            bounds.Encapsulate(ray.origin + ray.direction.normalized * distance);

            Get(bounds, raycastObjects, layerMask);

            IntersectInfo intersect ;
            bool hit = false;

            for (int i = 0; i < raycastObjects.Count; i++)
            {
                OctreeObject obj = raycastObjects[i];
                if (!obj.Intersects(ray, distance, out intersect))
                    continue;

                if (!hit)
                {
                    info = intersect;
                    hit = true;
                }
                else 
                {
                    if (Vector3.Distance(ray.origin, info.point) > Vector3.Distance(ray.origin, intersect.point))
                        info = intersect;
                }
            }
              
            return hit;
        }
         
        public static bool Raycast(Ray ray, float distance , out IntersectInfo info) 
        {
            return Raycast(ray, distance,Physics.AllLayers, out info);
        }

        public static void RaycastAll(Ray ray, float distance, LayerMask layerMask,List<IntersectInfo> results)
        { 
            if (distance <= 0)
                return;

            if (results == null)
                throw new System.Exception("results is null!");

            results.Clear();
             
            Bounds bounds = new Bounds();
            bounds.Encapsulate(ray.origin);
            bounds.Encapsulate(ray.origin + ray.direction.normalized * distance);

            Get(bounds, raycastObjects, layerMask);
             
            for (int i = 0; i < raycastObjects.Count; i++)
            {
                IntersectInfo intersect;
                OctreeObject obj = raycastObjects[i];
                if (!obj.Intersects(ray, distance, out intersect))
                    continue;

                results.Add(intersect);
            } 
        }

        public static void RaycastAll(Ray ray, float distance , List<IntersectInfo> results) 
        {
            RaycastAll(ray,distance,Physics.AllLayers,results);
        }


        // 扩容的话可以考虑异步扩容 TODO 


        private static OctreeArea GetRoot(int layer) 
        {
            if(Roots.ContainsKey(layer))
                return Roots[layer];

            OctreeArea Root = new OctreeArea(null, new Bounds(Vector3.zero, Vector3.one * 100),layer);
            Roots.Add(layer, Root);
            return Root;
        }



        /// <summary>
        /// 设置单位区域的最小范围(默认:1)
        /// </summary>
        public static void SetAreaMiniSize(int layer,float size) 
        {
            // 最小是1
            if (size < 1)
                size = 1;

            if( MinAreaSize.ContainsKey(layer))
                MinAreaSize[layer] = size;
            else
                MinAreaSize.Add(layer, size);
        }

        public static float GetAreaMiniSize(int layer)
        {
            if(MinAreaSize.ContainsKey(layer))
                return MinAreaSize[layer];

            return 1; 
        }

    }

}

